package com.glela.api.pay.wechat.component.impl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.DigestException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.glela.api.model.PayAppKeyInfo;
import com.glela.api.pay.wechat.component.WeChat;
import com.glela.api.pay.wechat.vo.JsapiTicketVo;
import com.glela.api.pay.wechat.vo.WeChatTokenVo;
import com.glela.api.pay.wechat.vo.WeChatUserInfoVo;
import com.glela.common.util.HttpUtil;
import com.glela.common.util.MD5;
import com.glela.common.util.RandomUtil;
import com.glela.common.util.Sha1;
import com.glela.common.util.XmlUtil;

/**
 * @name 微信工具抽象类
  *@author liujie
  *@2016年11月1日
 */
public abstract class AbstractWeChatImpl implements WeChat{
	
	private static final Logger LOGGER = LoggerFactory.getLogger(AbstractWeChatImpl.class);
	
	/**
	 * 获得分享accessToken
	 * @param appid
	 * @param secret
	 * @return
	 * @throws IOException
	 */
	@Override
	public WeChatTokenVo getAccessToken(String appid ,String secret) throws IOException{
		StringBuffer URL = new StringBuffer() ;
		URL.append("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=")
			.append(appid).append("&secret=").append(secret);
		String json = HttpUtil.doGet(URL.toString()) ;
		WeChatTokenVo vo = JSONObject.parseObject(json, WeChatTokenVo.class);
		return vo ;
	}

	/**
	 * 获得分享签名
	 * @param params
	 * @return
	 * @throws DigestException 
	 * @throws UnsupportedEncodingException 
	 */
	@Override
	public String getShareSign(Map<String, String> params) throws DigestException, UnsupportedEncodingException{
		return Sha1.SHA1(createSign(params)).toLowerCase();
	}
	
	/**
	 * 获得分享用的JsapiTicket
	 * @param accessToken
	 * @return
	 * @throws IOException
	 */
	@Override
	public JsapiTicketVo getJsapiTicket(String accessToken) throws IOException{
		StringBuffer URL = new StringBuffer() ;
		URL.append("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=")
			.append(accessToken).append("&type=jsapi");
		String json = HttpUtil.doGet(URL.toString()) ;
		JsapiTicketVo vo = JSONObject.parseObject(json, JsapiTicketVo.class);
		return vo ;
	}

	/**
	 * 微信授权   通过code换取网页授权access_token
	 * @param appid
	 * @param secret
	 * @param code
	 * @return
	 */
	@Override
	public WeChatTokenVo getWeChaAuthority(String appid , String secret ,String code ) throws IOException{
		StringBuffer URL = new StringBuffer() ;
		URL.append("https://api.weixin.qq.com/sns/oauth2/access_token?appid=")
			.append(appid).append("&secret=").append(secret)
			.append("&code=").append(code)
			.append("&grant_type=authorization_code");
		String json = HttpUtil.doGet(URL.toString()) ;
		WeChatTokenVo vo = JSONObject.parseObject(json, WeChatTokenVo.class);
		return vo ;
	}
	
	/**
	 * 刷新access_token
	 * @param appid
	 * @param refresh_token
	 * @return
	 * @throws IOException
	 */
	@Override
	public WeChatTokenVo getRefreshToken(String appid , String refreshToken ) throws IOException{
		StringBuffer URL = new StringBuffer() ;
		URL.append("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=")
			.append(appid).append("&grant_type=").append(refreshToken)
			.append("&refresh_token=REFRESH_TOKEN");
		String json = HttpUtil.doGet(URL.toString()) ;
		WeChatTokenVo vo = JSONObject.parseObject(json, WeChatTokenVo.class);
		return vo ;
	}
	
	/**
	 * 拉取用户信息(需scope为 snsapi_userinfo)
	 * @param accessToken   网页授权接口调用凭证
	 * @param openid    微信用户唯一id
	 * @return
	 * @throws IOException
	 */
	@Override
	public WeChatUserInfoVo getUserInfo(String accessToken , String openId ) throws IOException{
		StringBuffer URL = new StringBuffer() ;
		URL.append("https://api.weixin.qq.com/sns/userinfo?access_token=")
			.append(accessToken).append("&openid=").append(openId)
			.append("&lang=zh_CN");
		String json = HttpUtil.doGet(URL.toString()) ;
		WeChatUserInfoVo vo = JSONObject.parseObject(json, WeChatUserInfoVo.class);
		return vo ;
	}
	
	/**
	 * 检验授权凭证（access_token）是否有效
	 * @param accessToken
	 * @param openid
	 * @return
	 * @throws IOException
	 */
	@Override
	public WeChatTokenVo getIsOkAccessToken(String accessToken , String openId ) throws IOException{
		StringBuffer URL = new StringBuffer() ;
		URL.append("https://api.weixin.qq.com/sns/auth?access_token=")
			.append(accessToken).append("&openid=").append(openId)
			.append("&refresh_token=REFRESH_TOKEN");
		String json = HttpUtil.doGet(URL.toString()) ;
		WeChatTokenVo vo = JSONObject.parseObject(json, WeChatTokenVo.class);
		return vo ;
	}
	
	protected Map<String, String> getWeChaPayMap(PayAppKeyInfo payAppKeyInfo, String orderSn, String payNo, int totalFee, String spbillCreateIp, String openId) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("appid", payAppKeyInfo.getAppId());
		map.put("mch_id", payAppKeyInfo.getPartnerId()); //商户号
		//随机数 
		String nonce_str = RandomUtil.getRandomNumber(10);

		map.put("nonce_str", nonce_str);

		StringBuilder body = new StringBuilder();
		body.append(payAppKeyInfo.getStoreName() + "：").append(orderSn);
		map.put("body", body.toString());

		map.put("attach", "H5");

		map.put("out_trade_no", payNo);

		totalFee = totalFee <= 0 ? 999999999 : totalFee;
		map.put("total_fee", String.valueOf(totalFee));

		map.put("spbill_create_ip", spbillCreateIp);

		StringBuilder notify_url = new StringBuilder();
		notify_url.append(payAppKeyInfo.getCallbackUrl()).append("order/wechatPublicNotify/").append(orderSn).append("/").append(payNo);
		map.put("notify_url", notify_url.toString());

		map.put("trade_type", "JSAPI");
		map.put("openid", openId);
		return map;
	}
	
	/**
	 * MaoZeYao
	 * 微信支付会员Map
	 * @param payAppKeyInfo
	 * @param orderSn
	 * @param totalFee
	 * @param spbillCreateIp
	 * @param openId
	 * @return
	 */
	protected Map<String, String> getWeChaPayMapVip(PayAppKeyInfo payAppKeyInfo, String orderSn, int totalFee,
			String spbillCreateIp, String openId) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("appid", payAppKeyInfo.getAppId());
		map.put("mch_id", payAppKeyInfo.getPartnerId()); // 商户号
		// 随机数
		String nonce_str = RandomUtil.getRandomNumber(10);
		map.put("nonce_str", nonce_str);
		StringBuffer body = new StringBuffer();
		body.append(payAppKeyInfo.getStoreName() + "：").append(orderSn);
		map.put("body", body.toString());
		map.put("attach", "H5");
		map.put("out_trade_no", orderSn);
		totalFee = totalFee <= 0 ? 1 : totalFee;
		map.put("total_fee", String.valueOf(totalFee));
		map.put("spbill_create_ip", spbillCreateIp);
		StringBuffer notify_url = new StringBuffer();
		notify_url.append(payAppKeyInfo.getCallbackUrl()).append("order/wechatPublicNotifyVip");
		map.put("notify_url", notify_url.toString());
		map.put("trade_type", "JSAPI");
		map.put("openid", openId);
		return map;
	}
	
	/**
	 * MaoZeYao
	 * 微信退款
	 */
	@Override
	public Map<String, Object> weChaPayRefund(PayAppKeyInfo payAppKeyInfo, String orderSn, int totalFee, int refundFee,
			String afterSn, String payId) throws IOException {
		Map<String, String> map = weChaPayRefundMap(payAppKeyInfo, orderSn, totalFee, refundFee, afterSn, payId);
		// 获得签名
		StringBuffer sign = new StringBuffer();
		sign.append(createSign(map)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String signstr = MD5.md5(sign.toString()).toUpperCase();

		String xml = createXML(map, signstr);
		String url="https://api.mch.weixin.qq.com/secapi/pay/refund";
		String outxml = HttpUtil.doWeChatRefundPost(xml, url, payAppKeyInfo.getPartnerId(),payAppKeyInfo.getCa());
		Map<String, Object> outmap = XmlUtil.getMapByXml(outxml);
		return outmap;
	}
	/**
	* <dl>
	* <dt>微信退款查询</dt>
	* </dl>
	* @return Map<String, Object>
	* @author gaoweiqi
	 * @throws Exception 
	* @date 2017年12月07日 12:44:05
	*/
	@Override
	public Map<String, Object> weChaPayRefundQuery(PayAppKeyInfo payAppKeyInfo, String orderSn) throws Exception {
		Map<String, String> map = weChaPayRefundQueryMap(payAppKeyInfo,orderSn);
		// 获得签名
		StringBuffer sign = new StringBuffer();
		sign.append(createSign(map)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String signstr = MD5.md5(sign.toString()).toUpperCase();
		
		String xml = createXML(map, signstr);
		String url="https://api.mch.weixin.qq.com/pay/refundquery";
		String outxml = HttpUtil.doWeChatRefundQueryPost(xml, url,payAppKeyInfo.getCa(),payAppKeyInfo.getPartnerId());
		Map<String, Object> outmap = XmlUtil.getMapByXml(outxml);
		return removeNotNecessary(outmap);
	}
	
	/**
	* <dl>
	* <dt>微信订单查询</dt>
	* </dl>
	* @return Map<String, Object>
	* @author gaoweiqi
	 * @throws Exception 
	* @date 2017年12月13日 12:44:05
	*/
	@Override
	public Map<String, Object> weChaPayOrderQuery(PayAppKeyInfo payAppKeyInfo, String orderSn) throws Exception {
		Map<String, String> map = weChaPayOrderQueryMap(payAppKeyInfo,orderSn);
		// 获得签名
		StringBuffer sign = new StringBuffer();
		sign.append(createSign(map)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String signstr = MD5.md5(sign.toString()).toUpperCase();
		
		String xml = createXML(map, signstr);
		String url="https://api.mch.weixin.qq.com/pay/orderquery";
		String outxml = HttpUtil.doWeChatRefundQueryPost(xml, url,payAppKeyInfo.getCa(),payAppKeyInfo.getPartnerId());
		Map<String, Object> outmap = XmlUtil.getMapByXml(outxml);
		return removeNotNecessary(outmap);
	}
	/**
	 * MaoZeYao 
	 * 微信退款
	 * @param payAppKeyInfo
	 * @param orderSn
	 * @param totalFee
	 * @param spbillCreateIp
	 * @param openId
	 * @return
	 * @throws IOException
	 */
	protected Map<String, String> weChaPayRefundMap(PayAppKeyInfo payAppKeyInfo, String orderSn, int totalFee,
			 int refundFee, String afterSn, String payId) throws IOException{
		Map<String, String> map = new HashMap<String, String>();
		map.put("appid", payAppKeyInfo.getAppId() );  
		map.put("mch_id", payAppKeyInfo.getPartnerId() );  //商户号
		//随机数 
		String nonce_str = RandomUtil.getRandomNumber(10) ;
		map.put("nonce_str", nonce_str); 
		map.put("transaction_id", payId); // 微信订单号	
		map.put("out_trade_no", orderSn); 
		map.put("out_refund_no", afterSn); //退款单号   也就是售后单号
		map.put("total_fee", String.valueOf(totalFee));//单位为分     订单总金额
		map.put("refund_fee", String.valueOf(refundFee));// 退款金额   单位为分
		map.put("op_user_id", payAppKeyInfo.getPartnerId()); //操作员帐号, 默认为商户号   1900000109
		return map;
	}
	
	protected Map<String, String> weChaPayRefundQueryMap(PayAppKeyInfo payAppKeyInfo, String orderSn) throws IOException{
		Map<String, String> map = new HashMap<String, String>();
		map.put("appid", payAppKeyInfo.getAppId() );  
		map.put("mch_id", payAppKeyInfo.getPartnerId() );  //商户号
		//随机数 
		String nonce_str = RandomUtil.getRandomNumber(10) ;
		map.put("nonce_str",nonce_str); 
		map.put("out_trade_no", orderSn); 
		return map;
	}
	
	protected Map<String, String> getWeChaPayTransfersMap(PayAppKeyInfo payAppKeyInfo, String orderSn,
			int totalFee,String spbillCreateIp,String openId){
		Map<String, String> map = new HashMap<String, String>();
		map.put("mch_appid", payAppKeyInfo.getAppId() );  
		map.put("mchid", payAppKeyInfo.getPartnerId() );  //商户号
		//随机数 
		String nonce_str = RandomUtil.getRandomNumber(10) ;
		map.put("nonce_str", nonce_str); 
		map.put("partner_trade_no", orderSn); 
		map.put("openid", openId); 
		map.put("check_name", "OPTION_CHECK"); 
		map.put("amount", String.valueOf(totalFee));
		map.put("desc", "转账");
		map.put("spbill_create_ip", spbillCreateIp); 
		return map;
	}
	protected Map<String, String> weChaPayOrderQueryMap(PayAppKeyInfo payAppKeyInfo, String orderSn) throws IOException{
		Map<String, String> map = new HashMap<String, String>();
		map.put("appid", payAppKeyInfo.getAppId() );  
		map.put("mch_id", payAppKeyInfo.getPartnerId() );  //商户号
		//随机数 
		String nonce_str = RandomUtil.getRandomNumber(10) ;
		map.put("nonce_str",nonce_str); 
		map.put("out_trade_no", orderSn); 
		return map;
	}
	
	protected Map<String, Object> removeNotNecessary(Map<String, Object> outmap){
		//去除不必要的数据
		outmap.remove("appid");outmap.remove("mch_id");outmap.remove("nonce_str");
		outmap.remove("sign");outmap.remove("device_info");
		outmap.remove("openid");outmap.remove("is_subscribe");
		outmap.remove("transaction_id");
		return outmap;
	}
	@Override
	public Map<String, Object> weChaPayTransfers(PayAppKeyInfo payAppKeyInfo, String orderSn,
			int totalFee,String spbillCreateIp,String openId) throws IOException {
		Map<String, String> map = getWeChaPayTransfersMap(payAppKeyInfo, orderSn, totalFee, spbillCreateIp, openId);
		// 获得签名
		StringBuffer sign = new StringBuffer();
		sign.append(createSign(map)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String signstr = MD5.md5(sign.toString()).toUpperCase();

		String xml = createXML(map, signstr);
		String url="https://api.mch.weixin.qq.com/secapi/pay/refund";
		String outxml = HttpUtil.doWeChatRefundPost(xml, url, payAppKeyInfo.getPartnerId(),payAppKeyInfo.getCa());
		Map<String, Object> outmap = XmlUtil.getMapByXml(outxml);
		return outmap;
	}
	
	/**
	 * 微信支付
	 * @param map
	 * @param key 
	 * @return
	 * @throws IOException 
	 */
	@Override
	public Map<String, Object> weChaPay(PayAppKeyInfo payAppKeyInfo, String orderObjectOrderSn, String payNo, int totalFee, String spbillCreateIp, String openId) throws IOException {
		Map<String, String> map = getWeChaPayMap(payAppKeyInfo, orderObjectOrderSn, payNo, totalFee, spbillCreateIp, openId);
		//获得签名
		StringBuilder sign = new StringBuilder();
		sign.append(createSign(map)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String signstr = MD5.md5(sign.toString()).toUpperCase();

		String xml = createXML(map, signstr);
		String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
		String contentType = "text/xml";
		//		LOGGER.warn("微信支付{}-{}请求参数 ：{}", orderObjectOrderSn, payNo, xml);
		String outxml = HttpUtil.doPost(xml, url, contentType);
		//		LOGGER.warn("微信支付{}-{}返回结果 ：{}", orderObjectOrderSn, payNo, outxml);
		Map<String, Object> outmap = XmlUtil.getMapByXml(outxml);
		//		LOGGER.warn("微信支付{}-{}返回结果 ：{}", orderObjectOrderSn, payNo, JSON.toJSONString(outmap));
		if (!"SUCCESS".equals(outmap.get("return_code"))) {
			LOGGER.warn("微信支付{}-{}请求参数 ：{}", orderObjectOrderSn, payNo, xml);
			LOGGER.warn("微信支付{}-{}返回结果 ：{}-{}", orderObjectOrderSn, payNo, JSON.toJSONString(outmap), outxml);
			return outmap;
		}
		createPaySign(outmap, payAppKeyInfo);
		return outmap;
	}
	
	/**
	 * MaoZeYao
	 */
	@Override
	public Map<String, Object> weChaPayVip(PayAppKeyInfo payAppKeyInfo,String orderSn
			,int totalFee,String spbillCreateIp,String openId) throws IOException {
		Map<String, String> map=getWeChaPayMapVip(payAppKeyInfo, orderSn, totalFee, spbillCreateIp, openId);
		//获得签名
		StringBuffer sign = new StringBuffer();
		sign.append(createSign(map)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String signstr = MD5.md5(sign.toString()).toUpperCase();
		
		String xml = createXML(map, signstr);
		String url="https://api.mch.weixin.qq.com/pay/unifiedorder";
		String contentType="text/xml";
		String outxml = HttpUtil.doPost(xml,url,contentType);
		Map<String, Object> outmap = XmlUtil.getMapByXml(outxml);
		if (!"SUCCESS".equals(outmap.get("return_code"))) {
			return outmap ;
		}
		
		createPaySign(outmap, payAppKeyInfo);
		
		return outmap ;
	}
	
	protected void createPaySign(Map<String, Object> outmap,PayAppKeyInfo payAppKeyInfo) throws UnsupportedEncodingException
	{
		outmap.put("timeStamp", System.currentTimeMillis() / 1000);
		
		Map<String, String> paySignMap = new HashMap<String ,String >() ;
		paySignMap.put("appId", outmap.get("appid").toString());
		paySignMap.put("timeStamp", outmap.get("timeStamp").toString());
		paySignMap.put("nonceStr", outmap.get("nonce_str").toString());
		
		StringBuffer packageValue = new StringBuffer();
		packageValue.append("prepay_id=").append(outmap.get("prepay_id"));
		paySignMap.put("package", packageValue.toString());
		
		paySignMap.put("signType", "MD5");
		//获得支付签名
		StringBuffer paysign = new StringBuffer();
		paysign.append(createSign(paySignMap)).append("&key=").append(payAppKeyInfo.getPartnerKey());
		String paysignStr = MD5.md5(paysign.toString()).toUpperCase();
		
		//将支付签名添加到返回消息中
		outmap.put("paySign", paysignStr) ;
	}
	
	/**
     * 构造签名
     * @param params
     * @param encode
     * @return
     * @throws UnsupportedEncodingException 
     */
	protected String createSign(Map<String, String> params) throws UnsupportedEncodingException {
        Set<String> keysSet = params.keySet();
        //微信要求 参数名ASCII字典序排序
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        boolean first = true;
        for (Object key : keys) {
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            Object value = params.get(key);
            String valueString = "";
            if (null != value) {
                valueString = value.toString();
            }
            temp.append(valueString);
            //temp.append(URLEncoder.encode(valueString, "UTF-8"));
        }
        return temp.toString();
    }
    
    /**
     * 构造xml
     * @param params
     * @param encode
     * @return
     * @throws UnsupportedEncodingException 
     */
    private String createXML(Map<String, String> params, String sign) throws UnsupportedEncodingException {
        Set<String> keysSet = params.keySet();
        //微信要求 参数名ASCII字典序排序
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        temp.append("<xml>") ;
        for (Object key : keys) {
            temp.append("<").append(key).append(">");
            Object value = params.get(key);
            String valueString = "";
            if (null != value) {
                valueString = value.toString();
            }
            temp.append(valueString);
            temp.append("</").append(key).append(">");
        }
        
        temp.append("<sign>").append(sign).append("</sign>").append("</xml>") ;
        return temp.toString();
    }
    
    @Override
	public String getWeChatShortLink(String accessToken,String long_url) throws Exception{
    	Map<String,String> _map = new HashMap<String,String>();
		_map.put("action", "long2short");
		_map.put("long_url", long_url);
		JSONObject json = JSONObject.parseObject(JSON.toJSONString(_map));
		String ret = HttpUtil.doPost(json.toString(), "https://api.weixin.qq.com/cgi-bin/shorturl?access_token=" + accessToken , "text/xml");
		//{"errcode":0,"errmsg":"ok","short_url":"https://w.url.cn/s/A5XbIn4"}
		JSONObject retObject = JSON.parseObject(ret);
		if(retObject.getString("short_url") == null) {
			LOGGER.error("调用微信短链接返回值错误,却少短链接地址,用原长链接填充,微信返回值为:  {} ,  长链接地址为: {}",  retObject.toString() ,long_url);
			retObject.put("short_url", long_url);
		}
		return retObject.getString("short_url") ;
    }
    
}
